﻿/* $Author: vessimir@gmail.com $
 * $Id: LabyrinthModel.java 9 2011-11-13 21:13:22Z vessimir@gmail.com $
 * 
 * Модель лабиринта. Класс реализовывает все вычисления, позволяет, запрещает
 * перемещать бегунок, возвращает бегунок в начало лабиринта. 
 */
package ru.habrahabr.prettyalarm;

import java.util.ArrayList;

import android.content.Context;
import android.view.WindowManager;

/**
 * Модель лабиринта. Класс реализовывает все вычисления, позволяет, запрещает
 * перемещать бегунок, возвращает бегунок в начало лабиринта.
 * @author basargin
 */
public class LabyrinthModel
{
	// чем меньше допуск, тем сложнее проходить лабиринт
	private static final int ALLOWABLE_RUNNER_BOUNDS = 40;
	
	public ArrayList<Point> route = null; // TODO предоставлять public-доступ к полю нехорошо, но мне лень
	public Point runner = null;
	private float dip_scale;
	private int scalled_runner_bounds;
	private int stage;
	private OnLabyrinthEventHandler handler = null;
	
	/**
	 * Конструктор модели лабиринта.
	 * @param context контекст для вычисления dpi scale
	 */
	LabyrinthModel(Context context)
	{
		dip_scale = context.getResources().getDisplayMetrics().density;
		scalled_runner_bounds = (int)(dip_scale * ALLOWABLE_RUNNER_BOUNDS);
		WindowManager mWinMgr = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		int maxX = mWinMgr.getDefaultDisplay().getWidth();
		int maxY = mWinMgr.getDefaultDisplay().getHeight();
		generateRoute(maxX, maxY);
	}
	
	/**
	 * Инициализатор обработчика лабиринта.
	 * @param handler обработчик
	 */
	public void setOnLabyrinthEventHandler(OnLabyrinthEventHandler handler)
	{
		this.handler = handler;
	}
	
	/**
	 * Добавляет новую точку в лабиринт по указанных смещениям, относительно
	 * предыдущей точки. Первая созданная точка смещается относительно координат
	 * x=0 и y=0.
	 * @param x_offset смещение по оси x
	 * @param y_offset смещение по оси y
	 */
	private void addPoint(int x_offset, int y_offset)
	{
		x_offset = (int)(dip_scale*x_offset);
		y_offset = (int)(dip_scale*y_offset);
		if (route.size() == 0)
		{
			runner = new Point(x_offset, y_offset);
			route.add(new Point(runner));
		}
		else
		{
			Point last = route.get(route.size()-1);
			route.add(new Point(last.x+x_offset, last.y+y_offset));
		}
	}
	
	/**
	 * Генератор маршрута лабиринта.
	 */
	public void generateRoute(int maxX, int maxY)
	{
		// первая точка находится на maxY/2 по Y и +40 пикселей по X
		// TODO написать автогенератор пути
		route = new ArrayList<Point>();
		addPoint(40, (int)maxY/2);
		addPoint(70, 0);
		addPoint(0, 75);
		addPoint(165, 0);
		addPoint(0, -135);
		addPoint(-95, 0);
		addPoint(0, 75);
		stage = 0;
	}
	
	/**
	 * Метод обработки уведомления о перемещении бегунка лабиринта.
	 * @param x новая координата бегунка по оси x
	 * @param y новая координата бегунка по оси y
	 */
	public void supplyRunnerPosition(int x, int y)
	{
		if (stage < 0) return;
		
		final Point from = route.get(stage);
		final Point to = route.get(stage+1);
		// рассчитываем допустимую границу перемещения пальца
		final Point bndlt = new Point(Math.min(from.x,to.x)-scalled_runner_bounds, Math.min(from.y,to.y)-scalled_runner_bounds);
		final Point bndrb = new Point(Math.max(from.x,to.x)+scalled_runner_bounds, Math.max(from.y,to.y)+scalled_runner_bounds);
		// если палец вышел за границу перемещения, то возвращаем бегунок
		// в начало этапа, обнуляем stage
		if (x < bndlt.x || x > bndrb.x || y < bndlt.y || y > bndrb.y)
		{
			if (stage == 0) return;
			stage = 0;
			runner.copyFrom(route.get(0));
			// уведомляем подписчика, например для включения вибратора
			if (handler != null)
				handler.onRollbackToBeginning();
			// Log.v("stage", String.valueOf(stage)); 
			return;
		}
		// проверяем переход на следующий этап
		// через узловую точку палец надо проводить аккуратнее и точнее,
		// нежели следовать по пути
		final int half = scalled_runner_bounds / 2;
		final Point to_bndlt = new Point(to.x-half, to.y-half);
		final Point to_bndrb = new Point(to.x+half, to.y+half);
		if (x > to_bndlt.x && x < to_bndrb.x && y > to_bndlt.y && y < to_bndrb.y)
		{
			stage++;
			// уведомляем подписчика, например для включения вибратора
			if (handler != null)
				handler.onPassStage();
			// Log.v("stage", String.valueOf(stage));
			// если достигли конца маршрута, то прекращаем активность в этом окне,
			// создаём Intent для завершения текущего Activity
			if ((stage+1) == route.size())
			{
				stage = -1;
				if (handler != null)
					handler.onFinished();
			}
			return;
		}
		
		// если бегунок перемещается по вертикальной линии, то меняется только
		// координата по оси y, в противнос случае по x
		if (from.x == to.x)
		{
			runner.x = from.x;
			runner.y = y;
		}
		else
		{
			runner.x = x;
			runner.y = from.y;
		}
	}
	
	/**
	 * Сброс пройдённого бегунком пути. Перемещение бегунка в начало.
	 */
	public void resetRunner()
	{
		runner.copyFrom(route.get(0));
	}
	
	/**
	 * Класс с описанием "точки". Хранит координаты по осям x и y.
	 */
	public class Point
	{
		int x;
		int y;
		Point(Point p)
		{
			x = p.x;
			y = p.y;
		}
		Point(int x, int y)
		{
			this.x = x;
			this.y = y;
		}
		void copyFrom(final Point src)
		{
			x = src.x;
			y = src.y;
		}
	}
}
